<html>
<head>
<title> test </title>

<style>
ul {
    list-style-type: none;
    margin: 0;
    padding: 0;
    overflow: hidden;
}

li {
    float: left;
}

a:link, a:visited {
    display: block;
    width: 120px;
    font-weight: bold;
    color: #FFFFFF;
    background-color: #98bf21;
    text-align: center;
    padding: 4px;
    text-decoration: none;
    text-transform: uppercase;
}

a:hover, a:active {
    background-color: #7A991A;
}
#viewport {
    margin: 0;
    padding: 0;
}
body {
    margin: 0;
    padding: 0;
}
</style>
<script>

// debug drawing helper
var DebugDraw = new function () 
{
	var color = "#000000";
	var font = "10px sans serif";
	
	this.rect = function(ctx, x, y) {
		ctx.fillStyle = color;
		ctx.fillRect(x, y, 10, 10);
	}
	this.grid = function(ctx, x, y) {
		ctx.fillStyle = color;
		for (var i = 0; i < 10; i++) {
			ctx.fillRect(x + i * 20, y, 1, 200);
		}
	}
	this.text = function(ctx, x, y, text) {
		ctx.fillStyle = color;
		ctx.font = font;
		ctx.textAlign = 'center';
		ctx.fillText(text, x, y);
	}
	this.msg = "foo";
	this.drawMsg = function(ctx)
	{
		if (this.msg)
		{
			this.text(ctx,10,10,this.msg);
		}
	}
}

// Node functionality namespace
var Node = new function () 
{
	var width = 100;
	var height = 14;
	var padding = 2;
	var bgColor = "#CCCCCC";
	var fgColor = "#000000";
	var aColor = "#999999";
	var bColor = "#777777";
	var font = "10px sans serif";
	var activeNode = false;
	
	this.Header = 0;
	
	var nodes = [];
	this.setData = function(ns)
	{
		nodes = ns;
	}
	this.getData = function()
	{
		return nodes;
	}
	this.reset = function()
	{
		activeNode = false;
	}
	var drawNode = function (ctx, node, active) 
	{
		ctx.fillStyle = bColor;
		ctx.fillRect(node.p.x, node.p.y, width, height * node.e.length);
		for (var i = 0; i < node.e.length; i++) {
			var tx = node.p.x + padding;
			var ty = node.p.y + i * height + padding;
			var tw = width - padding * 2;
			var th = height - padding * 2;
			if (active && activeNode.i==i) {
				ctx.fillStyle = aColor;
			} else {
				ctx.fillStyle = bgColor;
			}

			ctx.font = font;
			ctx.textAlign = node.e[i].a;
			if (node.e[i].a == 'center') {
				var adjust = tw / 2;
				ctx.fillRect(tx, ty, tw, th);
				ctx.fillStyle = fgColor;
				ctx.fillText(node.e[i].t, tx + adjust, ty + th - padding);
			} else if (node.e[i].a == 'right') {
				var adjust = tw - th;
				ctx.fillRect(tx + adjust, ty, th, th);
				ctx.fillRect(tx, ty, tw - th - padding, th);
				ctx.fillStyle = fgColor;
				ctx.fillText(node.e[i].t, tx + adjust - padding * 2, ty + th - padding);
			} else {
				var adjust = th;
				ctx.fillRect(tx, ty, th, th);
				ctx.fillRect(tx + th + padding, ty, tw - th - padding, th);
				ctx.fillStyle = fgColor;
				ctx.fillText(node.e[i].t, tx + adjust + padding * 2, ty + th - padding);
			}
		}
	}
	
	this.draw = function(ctx)
	{
		for (var i = 0; i < nodes.length; i++) 
		{
			drawNode(ctx, nodes[i], activeNode && activeNode.n==i);
		}
	}
	this.isLeft = function (n,i) {
		return (nodes[n].e[i].a == 'left');
	}
	this.getPosition = function (n,i) {
		var tx = nodes[n].p.x + padding;
		var ty = nodes[n].p.y + i * height + padding;
		var tw = width - padding * 2;
		var th = height - padding * 2;
		if (nodes[n].e[i].a == 'right') {
			return {
				x : (tx + tw - (th / 2)),
				y : (ty + (th / 2)),
				d : (tw / 2)
			};
		}
		return {
			x : tx + th / 2,
			y : ty + th / 2,
			d : -tw / 2
		};
	}
	this.setPosition = function (n, p) {
		nodes[n].p.x = p.x - width / 2;
		nodes[n].p.y = p.y - height / 2;
	}
	var insideNode = function (p, node) {
		if (node.p.x < p.x && node.p.y < p.y && (node.p.x + width) > p.x && (node.p.y + node.e.length * height) > p.y) 
		{
			return Math.floor((p.y - node.p.y) / height);
		}
		return -1;
	}
	this.getNode = function(p)
	{
		activeNode = false;
		for (var i=0; i<nodes.length; i++)
		{
			var pressed = insideNode(p, nodes[i]);
			if (pressed != -1)
			{
				activeNode = {n:i, i:pressed};
				return activeNode;
			}
		}
		return false;
	}
	
}

var Link = new function()
{
	var links = [];
	var tmpLink = false;
	this.getData = function() 
	{
		return links;
	}
	this.setData = function(ls)
	{
		links = ls;
	}
	this.setTmpLink = function(n1,n2,p)
	{
		tmpLink = {n1:n1,n2:n2,p:p};
	}
	this.tryAddTmpLink = function()
	{
		if (tmpLink && tmpLink.n2)
		{
			this.remove(tmpLink.n1);
			this.remove(tmpLink.n2);
			links[links.length] = { n1:tmpLink.n1, n2:tmpLink.n2};
		}
		tmpLink = false;
	}
	this.draw = function(ctx)
	{
		ctx.beginPath();
		if (tmpLink) 
		{
			var sP = Node.getPosition(tmpLink.n1.n, tmpLink.n1.i);
			ctx.moveTo(sP.x, sP.y);
			ctx.quadraticCurveTo(sP.x + sP.d, sP.y, tmpLink.p.x, tmpLink.p.y);
			ctx.lineWidth = 3;
			ctx.stroke();
		}
		for (var i = 0; i < links.length; i++) 
		{
			var sP = Node.getPosition(links[i].n1.n, links[i].n1.i);
			var eP = Node.getPosition(links[i].n2.n, links[i].n2.i);
			var ceX = (sP.x + eP.x) * 0.5;
			var ceY = (sP.y + eP.y) * 0.5;
			ctx.moveTo(sP.x, sP.y);
			ctx.quadraticCurveTo(sP.x + sP.d, sP.y, ceX, ceY);
			ctx.quadraticCurveTo(eP.x + eP.d, eP.y, eP.x, eP.y);
			ctx.lineWidth = 3;
			ctx.stroke();
		}
	}
	
	this.remove = function(ni) 
	{
		for (var i = links.length - 1; i >= 0; i--) {
			if ((links[i].n1.n === ni.n && links[i].n1.i == ni.i) || (links[i].n2.n == ni.n && links[i].n2.i == ni.i)) {
				links.splice(i, 1);
			}
		}

	}
	this.endDraw = function()
	{
		wasTouch = false;
		wasTwoTouch = false;
		if (startTouch.n >= 0 && endTouch.n >= 0 && startTouch.d != endTouch.d && startTouch.n != endTouch.n) {
			removeLink(startTouch);
			removeLink(endTouch);
			links[links.length] = {
				n1 : startTouch,
				n2 : endTouch
			};
		}
		startTouch = {
			n : -1,
			i : -1,
			d : false
		};
		endTouch = {
			n : -1,
			i : -1,
			d : false
		};
		drawScene(ctx);
		drawMenus(ctx);
		SaveNodes();
	
	}
}
var Camera = new function()
{
	var tmpP = false;
	var tmpS = false;
	var tmpZ = false;
	var tmpK = false;
	
	var camera = {p:{x:0,y:0},s:1,f:{x:0,y:0}};
	var initScale = 1;
	this.setPosition = function(p)
	{
		camera.p = p;
	}
	
	this.setScale = function(s)
	{
		camera.s = Math.min(Math.max(0.1, s), 10);
	}
	this.transform = function(ctx)
	{
		ctx.translate(camera.p.x, camera.p.y);
		ctx.translate(camera.f.x, camera.f.y);
		ctx.scale(camera.s, camera.s);
		ctx.translate(-camera.f.x, -camera.f.y);
	}
	this.getWorld = function( p)
	{
		return {
			x : ((p.x-camera.p.x) - camera.f.x) / camera.s + camera.f.x,
			y : ((p.y-camera.p.y) - camera.f.y) / camera.s + camera.f.y
		};
	}
	this.reset = function(z)
	{
		var wp = this.getWorld(z);
		var cp = {x:z.x-wp.x, y:z.y-wp.y}; // set camera to upper corner
		tmpK = {x:z.x, y:z.y};
		tmpP = cp;
		camera.f = wp;
		tmpS = camera.s;
		tmpZ = z.l;
	}
	this.handle = function(z)
	{
		camera.s = Math.max(Math.max(z.l, 5) / Math.max(tmpZ, 5) * tmpS, 0.5);
		camera.s = Math.min(camera.s, 4);
		camera.p.x = tmpP.x + z.x - tmpK.x;
		camera.p.y = tmpP.y + z.y - tmpK.y;
		
		DebugDraw.msg = "x"+camera.p.x+ " y"+camera.p.y+" l"+z.l;
			
	}
}
var Scene = new function()
{
	var color = "#DDDDDD";
	this.clear = function(ctx, canvas)
	{
		var rectBound = canvas.getBoundingClientRect();
		ctx.setTransform(1, 0, 0, 1, 0, 0);
		ctx.fillStyle = color;
		ctx.fillRect(0, 0, rectBound.width, rectBound.height);
	}
	this.draw = function(ctx,canvas)
	{
		this.clear(ctx,canvas);
		Camera.transform(ctx);
		Node.draw(ctx);
		Link.draw(ctx);
		DebugDraw.drawMsg(ctx);
	}
}
var Touch = new function()
{
	this.isOne = function(event)
	{
		return (event.targetTouches.length == 1);
	}
	this.isTwo = function(event)
	{
		return (event.targetTouches.length == 2);
	}
	this.getPosition = function(event, rectClient, i)
	{
		return { 
			x : event.targetTouches[i].pageX - rectClient.left,
			y : event.targetTouches[i].pageY - rectClient.top
		};
	}
	this.getZoom = function(event,rectClient)
	{
		var p0 = this.getPosition(event,rectClient,0);
		var p1 = this.getPosition(event,rectClient,1);
		
		var diff = {
			x : p0.x - p1.x,
			y : p0.y - p1.y
		};
		
		return {
			x : (p0.x + p1.x)/2.0,
			y : (p0.y + p1.y)/2.0,
			l : Math.sqrt(diff.x * diff.x + diff.y * diff.y)
		};
	}
}

var Mouse = new function()
{
	var initPos = false;
	this.getPosition = function(event, rectClient)
	{
		return { 
			x : event.clientX - rectClient.left,
			y : event.clientY - rectClient.top
		};
	}
	this.reset = function()
	{
		initPos = false;
	}
	this.getZoom = function(event,rectClient)
	{
		var p0 = this.getPosition(event,rectClient);
		
		if (!initPos || event.which != 1)
		{
			initPos = {x:p0.x,y:p0.y};
		}
		
		return {
			x : p0.x,
			y : p0.y,
			l : Math.max(p0.x - initPos.x+20,1)
		};
	}
}
var MainLogic = new function()
{	
	var isDrag = false;
	var isZoom = false;
	this.doDrag = function(p)
	{		
		var wp = Camera.getWorld(p);
		if (!isDrag)
		{
			var nt = Node.getNode(wp);
			if (nt)
			{
				isDrag = nt;
			}
		}
		if (isDrag)
		{
			if (isDrag.i == Node.Header)
			{
				Node.setPosition(isDrag.n, wp);
			}
			else
			{
				var nt = Node.getNode(wp);
				if (nt.i == Node.Header)
				{
					nt = false;
				}
				Link.setTmpLink(isDrag,nt,wp);
			}
			return true;
		}
		return false;
	}
	this.doZoom = function(z)
	{
		if (!isZoom)
		{
			isZoom = true;
			Camera.reset(z);
		}
		else
		{
			Camera.handle(z);
		}
		return true;
	}
	this.endDrag = function()
	{
		if (isDrag)
		{
			isDrag = false;
			Link.tryAddTmpLink();
			Node.reset();
			return true;
		}
		return false;
	}
	this.endZoom = function()
	{
		if (isZoom)
		{
			isZoom = false;
			return true;
		}
		return false;
	}
	this.load = function(name)
	{
		if (!localStorage[name])
			return false;
		var data = JSON.parse(localStorage[name]);
		if (!data || !data.camera || !data.nodes || !data.links)
		{
			return false;
		}
		camera.setData(data.camera);
		Node.setData(data.nodes);
		links.setData(data.links);
		return true;
	}
	this.loadTmp = function(name)
	{
		Node.setData([
		{
			p:{x:10,y:10}, 
			e:[
				{a:'center', t:'foo'},
				{a:'left', t:'left'}
			]
		},
		{
			p:{x:50,y:40}, 
			e:[
				{a:'center', t:'bar'},
				{a:'right', t:'right'}
			]
		}
		]); // just to have something to work with
	}
	this.save = function(name)
	{
		var data = {
			camera:Camera.getData(),
			nodes:Node.getData(),
			links:Link.getData()
		};
		localStorage[name] = JSON.stringify(data);
	}
}
function doResize(ctx, canvas)
{
	canvas.width  = document.body.clientWidth;
	canvas.height  = document.body.clientHeight;
	Scene.draw(ctx,canvas);
}
window.addEventListener('resize', function(event)
{
	var	canvas = document.getElementById('viewport');
	var ctx = canvas.getContext("2d");
	doResize(ctx,canvas );
});
window.addEventListener('load', function () 
{
	var	canvas = document.getElementById('viewport');
	var ctx = canvas.getContext("2d");
    
	if (MainLogic.load("current") == false)
	{
		MainLogic.loadTmp();
	}
	
	canvas.addEventListener('touchend', function () 
	{
		MainLogic.endDrag();
		MainLogic.endZoom();
		Scene.draw(ctx,canvas);
		//MainLogic.save();
	}, false);
	
	canvas.addEventListener('touchmove', function (event) 
	{
		var rectBound = canvas.getBoundingClientRect();
		if (Touch.isOne(event)) 
		{
			var p = Touch.getPosition(event,rectBound,0);
			if (MainLogic.doDrag(p))
			{
				Scene.draw(ctx,canvas);
				event.preventDefault();
			}
		} 
		else
		{
			if (MainLogic.endDrag())
			{
				Scene.draw(ctx,canvas);
				event.preventDefault();
			}
		}
		if (Touch.isTwo(event)) 
		{
			var z = Touch.getZoom(event,rectBound);
			if (MainLogic.doZoom(z))
			{
				Scene.draw(ctx,canvas);
				event.preventDefault();
			}
			else
			{
				if (MainLogic.endZoom())
				{
					Scene.draw(ctx,canvas);
					event.preventDefault();
				}
			}
		}
	}, false);
	canvas.addEventListener('mouseup', function () 
	{
		MainLogic.endDrag();
		MainLogic.endZoom();
		Scene.draw(ctx,canvas);
		Mouse.reset();
		//MainLogic.save();
	}, false);
	
	canvas.addEventListener('mousemove', function(event) 
	{
	
		var rectBound = canvas.getBoundingClientRect();
		if (!event.altKey && event.which == 1) 
		{
			var p = Mouse.getPosition(event,rectBound);
			if (MainLogic.doDrag(p))
			{
				Scene.draw(ctx,canvas);
				event.preventDefault();
			}
		} 
		else
		{
			if (MainLogic.endDrag())
			{
				Mouse.reset();
				Scene.draw(ctx,canvas);
				event.preventDefault();
			}
		}
		if (event.altKey) 
		{
			var z = Mouse.getZoom(event,rectBound);
			if (MainLogic.doZoom(z))
			{
				Scene.draw(ctx,canvas);
				event.preventDefault();
			}
		}
		else
		{
			if (MainLogic.endZoom())
			{
				Mouse.reset();
				Scene.draw(ctx,canvas);
				event.preventDefault();
			}
		}
		
	}, false);
	  
	doResize(ctx,canvas );

}, false);
 </script>
 </head>
 <body>
 
<ul>
  <li><a href="#home">Home</a></li>
  <li><a href="#news">News</a></li>
  <li><a href="#contact">Contact</a></li>
  <li><a href="#about">About</a></li>
</ul>
 <canvas id = "viewport" />
 </body>
</html>
